package cn.edu.sjtu.stap.graph;

import java.util.*;

/**
 * This is the node in a graph.
 * 
 * @author William
 * 
 */
public class GraphNode extends GraphComponent {
    private static final long serialVersionUID = 1L;

    protected long id;

    protected String note;

    /**
     * Edges enter this node.
     */
    protected GraphEdgeSet incomingEdges = new GraphEdgeSet();

    /**
     * Edges leave this node.
     */
    protected GraphEdgeSet outgoingEdges = new GraphEdgeSet();

    /**
     * Nodes that have outgoing edges which enter this node.
     */
    protected GraphNodeSet sourceNodes = new GraphNodeSet();

    /**
     * Nodes that have incoming edges which leaves this node.
     */
    protected GraphNodeSet destinationNodes = new GraphNodeSet();

    /**
     * Constrcut this node with id.
     * 
     * @param id
     *            The id of this node.
     */
    public GraphNode(long id) {
        this(id, null);
    }

    /**
     * Construct this node with id and some notes.
     * 
     * @param id
     *            The id of this node.
     * @param s
     *            The note for this node.
     */
    public GraphNode(long id, String s) {
        this.id = id;
        this.note = s;
    }

    /**
     * Gets the id of this node.
     * 
     * @return id of this node.
     */
    public long getId() {
        return id;
    }

    /**
     * Gets the note of this node.
     * 
     * @return the note of this node.
     */
    public String getNote() {
        return note;
    }

    /**
     * Sets the note of this node.
     * 
     * @param s
     *            The note to be added for this node.
     */
    public void setNote(String s) {
        this.note = s;
    }

    /**
     * Add an incoming edge into this node. If the edge is in the incoming edge
     * set of this node, it does nothing. This method should be used only after
     * this node has been set as the destination node of the given edge.
     * 
     * @param edge
     *            The edge to add.
     * @return true if the edge is added, otherwise false.
     * @throws NullPointerException
     *             if the given edge is null.
     */
    protected boolean addIncomingEdge(GraphEdge edge) {
        if (null == edge) {
            throw new NullPointerException();
        }

        boolean b = incomingEdges.add(edge);
        if (b) {
            GraphNode n = edge.getSourceNode();
            if (null != n) {
                sourceNodes.add(n);
                n.destinationNodes.add(this);
            }
        }

        return b;
    }

    /**
     * Remove an incoming edge from this node. If the edge is not in the
     * incoming edge set of this node, it does nothing. After removing, the
     * relationships with this node are all cleared.
     * 
     * @param edge
     *            The edge to remove.
     * @return true if the edge is removed, otherwise false.
     * @throws NullPointerException
     *             if the given edge is null.
     */
    protected boolean removeIncomingEdge(GraphEdge edge) {
        if (null == edge) {
            throw new NullPointerException();
        }

        boolean b = incomingEdges.remove(edge);
        if (b) {
            GraphNode s = edge.getSourceNode();
            if (null != s) {
                sourceNodes.remove(s);
                s.destinationNodes.remove(this);

                edge.destinationNode = null;
            }
        }

        return b;
    }

    /**
     * Add an outgoing edge into this node. If the edge is in the outgoing edge
     * set of this node, it does nothing. This method should be used only after
     * this node has been set as the source node of the given edge.
     * 
     * @param edge
     *            The edge to add.
     * @return true if the edge is added, otherwise false.
     * @throws NullPointerException
     *             if the given edge is null.
     */
    protected boolean addOutgoingEdge(GraphEdge edge) {
        if (null == edge) {
            throw new NullPointerException();
        }

        boolean b = outgoingEdges.add(edge);
        if (b) {
            GraphNode n = edge.getDestinationNode();
            if (null != n) {
                destinationNodes.add(n);
                n.sourceNodes.add(this);
            }
        }

        return b;
    }

    /**
     * Remove an outgoing edge from this node. If the edge is not in the
     * outgoing edge set of this node, it does nothing. After removing, the
     * relationships with this node are all cleared.
     * 
     * @param edge
     *            The edge to remove.
     * @return true if the edge is added, otherwise false.
     * @throws NullPointerException
     *             if the given edge is null.
     */
    protected boolean removeOutgoingEdge(GraphEdge edge) {
        if (null == edge) {
            throw new NullPointerException();
        }

        boolean b = outgoingEdges.remove(edge);
        if (b) {
            GraphNode d = edge.getDestinationNode();
            if (null != d) {
                destinationNodes.remove(d);
                d.sourceNodes.remove(this);

                edge.sourceNode = null;
            }
        }

        return b;
    }

    /**
     * Get the set of incoming edges.
     * 
     * @return the set of incoming edges.
     */
    public GraphEdgeSet getIncomingEdgeSet() {
        return incomingEdges;
    }

    /**
     * Get the set of outgoing edges.
     * 
     * @return the set of outgoing edges.
     */
    public GraphEdgeSet getOutgoingEdgeSet() {
        return outgoingEdges;
    }

    /**
     * Get the set of source nodes that have outgoing edges to this node.
     * 
     * @return the set of source nodes.
     */
    public GraphNodeSet getSourceNodeSet() {
        return sourceNodes;
    }

    /**
     * Get the set of destination nodes that have incoming edges from this node.
     * 
     * @return the set of destination nodes.
     */
    public GraphNodeSet getDestinationNodeSet() {
        return destinationNodes;
    }

    /**
     * Check whether an edge is an incoming edge of this node.
     * 
     * @param edge
     *            The edge to check.
     * @return true if the given edge is an incoming edge of this node,
     *         otherwise false.
     */
    public boolean containsIncomingEdge(GraphEdge edge) {
        return incomingEdges.contains(edge);
    }

    /**
     * Check whether an edge is an outgoing edge of this node.
     * 
     * @param edge
     *            The edge to check.
     * @return true if the given edge is an outgoing edge of this node,
     *         otherwise false.
     */
    public boolean containsOutgoingEdge(GraphEdge edge) {
        return outgoingEdges.contains(edge);
    }

    /**
     * Check whether an edge is an incoming or outgoing edge of this node.
     * 
     * @param edge
     *            The edge to check.
     * @return true if the given edge is an incoming or outgoing edge of this
     *         node, otherwise false.
     */
    public boolean containsRelatedEdge(GraphEdge edge) {
        return containsIncomingEdge(edge) || containsOutgoingEdge(edge);
    }

    /**
     * Clear all the edges and nodes related to this node. That is to break the
     * relationship between the edges and this node.
     * 
     */
    public void clear() {
        clearIncomingEdges();
        clearOutgoingEdges();
    }

    /**
     * Clear all the incoming edges from the incoming edge set of this node.
     * 
     */
    public void clearIncomingEdges() {
        List<GraphEdge> toRemove = new ArrayList<GraphEdge>();
        Iterator<GraphEdge> i = incomingEdges.iterator();
        while (i.hasNext()) {
            toRemove.add(i.next());
        }
        i = toRemove.iterator();
        while (i.hasNext()) {
            removeIncomingEdge(i.next());
        }
    }

    /**
     * Clear all the outgoing edges from the outgoing edge set of this node.
     * 
     */
    public void clearOutgoingEdges() {
        List<GraphEdge> toRemove = new ArrayList<GraphEdge>();
        Iterator<GraphEdge> i = outgoingEdges.iterator();
        while (i.hasNext()) {
            toRemove.add(i.next());
        }
        i = toRemove.iterator();
        while (i.hasNext()) {
            removeOutgoingEdge(i.next());
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
        return (int) (id);
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(Object o) {
        return (o instanceof GraphNode ? this.id == ((GraphNode) o).id : false);
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#toString()
     */
    public String toString() {
        return "(Node " + id + ")";
    }
}
